import evented from './mixins/evented'
import stateful from './mixins/stateful'
import * as Events from './utils/events'
import * as Fn from './utils/fn'
import log from './utils/log'
import Player from './player'


/**
 * 基础的plugin名
 * @private
 * @constant
 */
const BASE_PLUGIN_NAME = 'plugin'

/**
 *
 */
const PLUGIN_CACHE_KEY = 'actionPlugins_'

/**
 * 存储注册了的plugins
 */
const pluginStorage = {}

const pluginExists = name => pluginStorage.hasOwnProperty(name)

const getPlugin = name => pluginExists(name) ? pluginStorage[name] : undefined

/**
 * 使得plugin为active状态
 * @param {*} player
 * @param {*} name
 */
const makePluginAsActive = (player, name) => {
  player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {}
  player[PLUGIN_CACHE_KEY][name] = true
}

/**
 * 触发插件安装事件
 * @param {Player} player
 * @param {Plugin~PluginEventHash} hash
 * @param {Boolean} before
 */
const triggerSetupEvent = (player, hash, before) => {
  const eventName = (before ? 'before' : '') + 'pluginsetup'
  player.trigger(eventName, hash)
  player.trigger(eventName + ':' + hash.name, hash)
}

/**
 *
 * @param {string} name plugin名字
 * @param {Function} plugin plugin
 * @returns {Function}
 */
const createBasicPlugin = function (name, plugin) {
  const basicPluginWrapper = function () {
    triggerSetupEvent(this, {name, plugin, instance: null}, true)

    const instance = plugin.apply(this, arguments)

    markPluginAsActive(this, name)
    triggerSetupEvent(this, {name, plugin, instance})
    return instance
  }

  Object.keys(plugin).forEach(function (prop) {
    basicPluginWrapper[prop] = plugin[prop]
  })
  return basicPluginWrapper
}

const createPluginFactory = (name, PluginSubClass) {
  PluginSubClass.prototype.name = name
  return function (...args) {
    triggerSetupEvent(this, {name, plugin: PluginSubClass, instance: null}, true)
    const instance = new PluginSubClass(...[this, ...args])
    this[name] = () => instance
    triggerSetupEvent(this, instance.getEventHash())
    return instance
  }
}


class Plugin {
  constructor (player) {
    if (this.constructor === Plugin) {
      throw new Error('Plugin必须是继承类, 不能直接初始化')
    }

    this.player = player

    evented(this)
    delete this.trigger // 删除evented后的trigger，这样就能使用原型上的了

    stateful(this, this.constructor.defaultState)
    markPluginAsActive(player, this.name)

    this.dipsose = Fn.bind(this, this.dipsose)

    // 如果播放器被清除，那么就清除当前插件
    player.on('dispose', this.dispose)
  }

  version () {
    return this.constructor.VERSION
  }

  /**
   * 每一被触发的事件都有一个额外的hash
   * @param {*} hash
   */
  getEventHash (hash = {}) {
    hash.name = this.name
    hash.plugin = this.constructor
    hash.instance = this
    return hash
  }

  trigger (event, hash = {}) {
    return Events.trigger(this.eventBusEl_, event, this.getEventHash(hash))
  }

  handleStateChanged (e) {}

  dispose () {
    const { name, player } = this
    this.trigger('dispose')
    this.off()
    player.off('dispose', this.dispose)
    player[PLUGIN_CACHE_KEY][name] = false

    player[name] = createPluginFactory(name, pluginStorage[name])
  }

  /**
   * 判断一个插件是否是基础插件
   */
  static isBasic (plugin) {
    const p = (typeof plugin === 'string') ? getPlugin(plugin) : plugin
    return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype)
  }

  static registerPlugin (name, plugin) {
    if (typeof name !== 'string') {
      throw new Error('name必须是字符串')
    }
    if (pluginExists(name)) {
      log.warn('插件已经存在')
    } else if (Player.prototype.hasOwnProperty(name)) {
      throw new Error('非法的插件名，不能和player的方法重名')
    }
    if (typeof plugin !== 'function') {
      throw new Error('插件必须是函数')
    }
    pluginStorage[name] = plugin

    if (name !== BASE_PLUGIN_NAME) {
      if (Plugin.isBasic(plugin)){
        Player.prototype[name] = createBasicPlugin(name, plugin)
      } else {
        Player.prototype[name] = createPluginFactory(name, plugin)
      }
    }
    return plugin
  }

  /**
   * 注销一个插件
   * @param {*} name
   */
  static deregisterPlugin (name) {
    if (name === BASE_PLUGIN_NAME) {
      throw new Error('不能注销基础插件')
    }
    if (pluginExists(name)) {
      delete pluginStorage[name]
      delete Player.prototype[name]
    }
  }

  static getPlugins (name = Object.keys(pluginStorage)) {
    let result
    names.forEach(name => {
      const plugin = getPlugin(name)

      if (plugin) {
        result = result || {}
        result[name] = plugin
      }
    })
    return result
  }

  static getPluginVersion (name) {
    const plugin = getPlugin(name)
    return plugin && plugin.VERSION || ''
  }
}

Plugin.getPlugin = getPlugin
Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME
Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin)

Player.prototype.usingPlugin = function (name) {
  return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true
}
Player.prototype.hasPlugin = function (name) {
  return !!pluginExists(name)
}

export default Plugin
